#
# CVS.py
#
# Main SNiFF interface for SNiFF+ 3.x CVS Adapter
#
# Author: Martin Oberhuber
# Copyright (c) 1998, 1999 TakeFive Software, Salzburg
#
# Revisions:
#   $Id: CVS.py,v 1.17 1999/11/09 12:55:58 mober Exp $
#   cvs-%VERSION%
#

import os, string, regsub, regex, sys, time
import vcs
from vcs import TEST, TRUE, FALSE, NL, SYSTEM, WINDOWS, UNIX
from vcs import NULLDEV, DELIMITER
from CVSlog import Cvslog

import CVSPrefs
import CVSCore
import CVSDialogs

# ---- log handling ----------------------------------

_last_work_file		= ''
_last_work_dir		= ''
_last_log_with_history	= FALSE
_last_log		= None

#
# log_without_history:
#	history output without comments
#
def log_without_history(workingDir, workingFile, use_cache=FALSE):
	global _last_work_file, _last_work_dir, _last_log_with_history, _last_log
	if use_cache and _last_work_file == workingFile and _last_work_dir == workingDir:
		if CVSPrefs.TEST: print "(CVS) log_nohist --> re-use ",workingFile
		_last_log.reuse()
		return _last_log
	else:
		if CVSPrefs.TEST: print "(CVS) log_nohist (",workingDir, ", ", workingFile, ")"
		if string.find(workingFile, ' ') > 0:	# multiple files
			os.chdir(workingDir)
			return Cvslog(workingFile, workingFile, '-h')
		_last_work_file = workingFile
		_last_work_dir = workingDir
		_last_log_with_history = FALSE
		# log works without CVS/Entries file as well
		os.chdir(workingDir)
		_last_log = Cvslog(workingFile, workingFile, '-h')
		_last_log.close()
		if not _last_log:
			_last_work_file = ''
		return _last_log


#
# log_with_history:
#	history output with comments
#
def log_with_history(workingDir, workingFile, use_cache=FALSE):
	global _last_work_file, _last_work_dir, _last_log_with_history, _last_log
	if use_cache and _last_log_with_history and (_last_work_file == workingFile) and (_last_work_dir == workingDir):
		if CVSPrefs.TEST: print "(CVS) log_hist --> re-use ",workingFile
		_last_log.reuse()
		return _last_log
	else:
		if CVSPrefs.TEST: print "(CVS) log_hist (",workingDir, ", ", workingFile, ")"
		if string.find(workingFile, ' ') > 0:	# multiple files #BUGBUG spaceInFileName
			os.chdir(workingDir)
			return Cvslog(workingFile, workingFile)
		_last_work_file = workingFile
		_last_work_dir = workingDir
		_last_log_with_history = TRUE
		# log works without CVS/Entries file as well
		os.chdir(workingDir)
		_last_log = Cvslog(workingFile, workingFile)
		_last_log.close()
		if not _last_log:
			_last_work_file = ''
		return _last_log

#
# cvsTagnameForRevision():
#
# This is a helper for CheckOut() to get correct sticky tag for a given version.
# If the version is a tip revision, we return the revArg needed to check it out.
# e.g. ''      --> return -A
# e.g. 1.5     --> if head==1.5, return -A
# e.g. 1.5.1.6 --> if 1.5.1.6 is HEAD_br_test, return -rbr_test
#                  else, return -r1.5.1.6
#
def cvsTagnameForRevision(workingDir, workingFile, revision):
	if not revision:
		return "-A"
	defaultResult = "-r%s" % revision
	log = log_with_history(workingDir, workingFile, TRUE)
	if not log: 
		return defaultResult
	if revision == log.head_rev():
		# is it the head of the main trunk?
		return "-A"
	revList = string.split(revision,".")
	if len(revList) > 2:
		# it's a branch: is it the head of the branch?
		theBranch = string.join(revList[:-1],".")
		maxRevInBranch = 0
		for rev in log.revision_list():
			if rev.rev[:len(theBranch)]==theBranch:
				# branch revisions are always sorted in reverse order, so the
				# first match is the branch'es tip revision.
				maxRevInBranch = rev.rev[len(theBranch)+1:]
				break
		if revList[-1:] == [ maxRevInBranch ] :
			# the version requested IS a tip revision: find the branch name
			for symdef in log.symbollist():
				if symdef[:13] == "SNIFF_BRANCH_" and symdef[-len(theBranch)-2:-2] == theBranch:
					sym, rev = string.split(symdef)
					return '-r%s' % sym[13:-1]
	return defaultResult

#
# Use cvs -nq update but return same return codes as diff would
# this should usually be faster than the cvs diff, especially
# because in case of revArg = "-A" we don't need to do two cvs commands
#
def cvsIsDifferent(workingDir, workingFile, sniffRevision):
	#DIFF_CMD = 'cvs diff -N %s' % (revArg)
	#if revArg == '-A':
	#	# Can't do -A because cvs diff cannot handle it!
	#	log = log_without_history(vcs.workingDir, vcs.workingFile, TRUE)
	#	if not log:
	#		return FALSE
	#	DIFF_CMD = 'cvs diff -N -r%s' % log.head_rev()
	if not sniffRevision:
		revArg='-A'
	else:
		revArg='-r'+sniffRevision
	cmd = "cd %s ; cvs -nq update %s %s" % (workingDir, revArg, workingFile)
	out = string.strip(vcs.getoutput(cmd))
	if not out:
		# no difference
		return 0
	elif out[1] != " ":
		# error
		return 2
	else:
		# files are different
		return 1

# find out if filename is a Project Description File
def isPDF(file):
	#file = string.strip(file)
	if file[-7:] == ".shared" or file[-5:] == ".proj":
		return TRUE
	else:
		return FALSE

#
# cvs edit a file; if it's a PDF, remove the Base/Filename
# to hide it from the OpenProjects Dialog
#
def cvsEdit(dir, file):
	rc = vcs.system( 'cd %s ; cvs edit %s;' % (dir, file) )
	if vcs.test(rc) and CVSPrefs.EditRemoveBaseForPDF and isPDF(file):
		vcs.rm_f(vcs.joinpath(dir,"CVS","Base",file))

#
# cvs unedit a file; if it's a PDF, re-create the Base/Filename
#
def cvsUnedit(dir, file, wasModified=0):
	if CVSPrefs.EditRemoveBaseForPDF and isPDF(file):
		cmd = 'cd %s ; rm -f %s ; cvs -r update %s ; cp %s CVS/Base ; cvs unedit %s' % (dir, file, file, file, file)
	else:
		if wasModified:
			cmd = "cd %s ; echo 'yes' | cvs -r unedit %s" % (dir, file)
		else:
			cmd = "cd %s ; cvs -r unedit %s" % (dir, file)
	rc = vcs.system(cmd)
	if rc==0:
		thePath = vcs.joinpath(dir,file)
		if vcs.is_writable(thePath):
			# file was offline edited only and was thus not set read only
			rc = vcs.chmod(thePath,"ugo-w")
	return rc

#
# Update a file, probably re-editing it if it was writable before
#
def cvsUpdate(dir, file, sniffRevision, doRemove=0, update_writable=0):
	if sniffRevision:
		revArg = cvsTagnameForRevision(dir, file, sniffRevision)
	else:
		# BUGBUG -A will destroy sticky options as well... is this OK?
		revArg = '-A'
	if doRemove:
		cmd = 'cd %s ; rm -f %s ; cvs -r -q update %s %s' % (dir, file, revArg, file)
	else:
		cmd = 'cd %s ; cvs -r update %s %s' % (dir, revArg, file)
	out = vcs.getoutput(cmd)
	# fix for files originally added on a branch!
	if string.find("cvs update: warning: new-born", vcs.err) >= 0:
		# had a local "dead" file newly added: re-do update!
		vcs.err=""
		vcs.out=""
		out = vcs.getoutput(cmd)
	outList = string.split(string.strip(out),NL)
	result = ''
	if outList[-1] and outList[-1][0] == "C":
		result = '@rcvs update: conflicts found in %s@r' % file + NL
	result = result + out
	if update_writable:
		if CVSPrefs.DoCVSEdit:
			if CVSPrefs.TEST: print "Re-editing file after update"
			#rc = vcs.system( 'cd %s ; cvs edit %s;' % (dir, file) )
			rc = cvsEdit(dir,file)
		else:
			if CVSPrefs.TEST: print "Re-setting to writable after update"
			rc = vcs.system( 'cd %s ; chmod u+w %s;' % (dir, file) )
	return result

#
# find the revision for a given tag name
# ATTENTION: Using already converted tag names (like SNIFF_BRANCH_xxx)
# 
def cvsRevisionOfSymbol(workingDir, workingFile, symbolName):
	symbol = symbolName
	log = log_without_history(workingDir, workingFile, TRUE)
	if not log:
		return ""
	sym_defs = log.symbollist() 
	rev = ''
	for sym_def in sym_defs:
		[sym_name, version] = string.split(sym_def,':')
		if sym_name == symbol:
			rev = string.lstrip(version)
			break
	if rev:
		return rev
	else:
		return ""

# ---- VCS commands ----------------------------------

#
# CheckOut:
# 	check out a file
# 	also needed for the three-way differences in the Config Manager: in that 
#   case, the workingDir will be something with /tmp...
#
def CheckOut():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BCheckOut@B"
	if CVSPrefs.TEST: print "RWE:       ",vcs.repositoryRoot
	if CVSPrefs.TEST: print "SSWE:      ",vcs.repositoryRoot
	if CVSPrefs.TEST: print "RCCS:      ",vcs.RCCSPath
	if CVSPrefs.TEST: print "PWE:       ",vcs.privateRoot
	if CVSPrefs.TEST: print "SSWS:      ",vcs.SSWS
	if CVSPrefs.TEST: print "WorkingDir:",vcs.workingDir
	if CVSPrefs.TEST: print "ProjectDir:",vcs.projectDir
	if CVSPrefs.TEST: print "catDir:    ",vcs.catDir
	if CVSPrefs.TEST: print "File:      ",vcs.workingFile
	if CVSPrefs.TEST: print "Version:   ",vcs.revision
	if CVSPrefs.TEST: print "branchName:",vcs.branchName
	if CVSPrefs.TEST: print "symbolName:",vcs.symbolName
	if CVSPrefs.TEST: print "forceFlag: ",vcs.forceFlag
	if CVSPrefs.TEST: print "lockType:  ",vcs.lockType
	#### step 0: find out where the file is in the Repository
	if vcs.repositoryDir:
		[ cvsroot, relative_cvs_path ] = string.split(vcs.repositoryDir, DELIMITER)
	else:
		# ( root, relative ) = CVSCore.GuessRoot(vcs.repositoryRoot, vcs.RCCSPath, vcs.workingDir)
		result = CVSDialogs.GuessRepositoryDialog(vcs.repositoryRoot, CVSCore.relative_dir(vcs.workingDir, vcs.projectDir, vcs.catDir))
		if not result:
			return FALSE
		(cvsroot, relative_cvs_path) = result
	relative_filename = vcs.joinpath(relative_cvs_path, vcs.workingFile)
	##### put together some arguments
	if not vcs.revision:
		revArg='-A'
	else:
		revArg='-r'+vcs.revision
	# checkout into tempDir: we don't want cvs admin. information here! 
	# So simply check out to stdout and pipe it into the file.
	if vcs.workingDir == vcs.tmpDir:
		if CVSPrefs.TEST: print "Check-out into Temp Dir!"
		os.chdir(vcs.tmpDir)
		args = (vcs.workingFile, cvsroot, revArg, relative_filename, vcs.workingFile, vcs.workingFile)
		# this is used for "show differences", so make it read-only!
		rc = vcs.system( 'rm -rf %s ; cvs -d %s -q checkout -p %s %s > %s; chmod ugo-w %s' % args )
		#vcs.chmod('ugo-w', vcs.joinpath(vcs.tmpDir,vcs.workingFile))
	else:
		try:
			os.chdir(vcs.workingDir)
		except:
			print "(CVS) Checkout ERROR: could not chdir to working Directory ",vcs.workingDir
			return FALSE
		if vcs.repositoryDir:
			cvsCommand = "update"
			relative_filename = vcs.workingFile
			# get correct tagname for sticky tag
			revArg = cvsTagnameForRevision(vcs.workingDir, vcs.workingFile, vcs.revision)
			doMove = 0
		else:
			cvsCommand = "co"
			# cannot yet get correct tagname because cvs log won't work
			doMove = 1
		######### step 1: check out or update
		lockArg = ''
		if vcs.lockType == 'N':
			lockArg = '-r'
		else:
			lockArg = '-w'
		args = (relative_filename, lockArg, cvsCommand, revArg, relative_filename)
		# remove any existing symlink since we want to get a LOCAL file
		rc = vcs.system( 'rm -f %s ; cvs %s %s %s %s;' % args )
		# fix for files originally added on a branch!
		if string.find("cvs update: warning: new-born", vcs.err) >= 0:
			# had a local "dead" file newly added: re-do update!
			rc = vcs.system( 'cvs %s update %s %s;' % (lockArg, revArg, relative_filename ))
		######## step 2: move it into place	
		if doMove:
			#BUGBUG the moving is really a bad idea because new directories won't
			#be registered at their parent's directories!
			rc = vcs.system( 'mv %s %s' % ( relative_filename, vcs.workingFile) )
			rc = vcs.system( 'mv %s %s' % ( vcs.joinpath(relative_cvs_path, "CVS"), vcs.workingDir) )
			relative_filename = vcs.workingFile
			newRevArg = cvsTagnameForRevision(vcs.workingDir, vcs.workingFile, vcs.revision)
			if newRevArg != revArg:
				rc = vcs.system( 'cvs %s update %s %s' % (lockArg, newRevArg, relative_filename))
		######## step 3a: if the file doesn't exist (because it was tryed to check out a
		#        "dead revision", e.g. because file was initially added on a branch,
		#        cvs will not create a local file. In that case, we must not do edit or locking.
		#        In order for DiffText to work, we need an empty file.
		os.chdir(vcs.workingDir)
		workingFile = vcs.joinpath(vcs.workingDir, vcs.workingFile)
		st = vcs.get_stat(workingFile)
		if not vcs.test_exists(st):
			# create an empty file
			if vcs.lockType == 'N':
				rc = vcs.system( 'touch %s ; chmod a-w %s' % (workingFile, workingFile) )
			else:
				rc = vcs.system( 'touch %s' % workingFile )
			if not vcs.test(rc):
				print "Error touching ",workingFile
				return FALSE
			rc = vcs.system( 'cvs add %s' % (vcs.workingFile))
			CVSCore.get_entries(vcs.workingDir, FALSE)
			#return vcs.test(rc)
			# return TRUE also if cvs add returned an error -- especially the 
			# "already added independently by second party" is not really an error.
			return TRUE
		######## step 3b: force reloading CVS Entries information
		CVSCore.get_entries(vcs.workingDir, FALSE)
		######## step 3: do the edit and locking
		if vcs.test(rc) and ( vcs.lockType == 'E' or vcs.lockType == 'C' ) :
			if CVSPrefs.DoCVSEdit:
				#rc = vcs.system( 'cvs edit %s;' % (relative_filename) )
				rc = cvsEdit(vcs.workingDir, vcs.workingFile)
		if vcs.test(rc) and vcs.lockType == 'E' :
			if not CVSPrefs.AllowExclusiveLocking:
				print "Exclusive Locking is disabled at your site. File is concurrently locked instead."
				return TRUE
			# BUGBUG must be disallowed if already locked by someone else!
			if vcs.revision:
				rc = vcs.system( 'cvs admin -q -l%s %s' % (vcs.revision, relative_filename) )
			else:
				rc = vcs.system( 'cvs admin -q -l %s' % (relative_filename) )
	return vcs.test(rc)


#
# CheckOutProject:
#	checks out all files of a project
#
def CheckOutProject():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BCheckOutProject@B"
	# this would allow for fast initial checkout. Currently, it is
	# disabled in the CVS.sniff adaptor since it would require all SNiFF
	# projects to be set in the modules file (otherwise a cvs co modulename 
	# won't work)
	# os.chdir(vcs.workingDir)
	# cmd = 'cvs -d %s co -r %s' % (cvsroot, vcs.projectName)
	return TRUE


#
# CreateRepositoryFile:
#	creates the repository file if not existant
#	not used with CVS because we can not really determine if the file 
#	really exists in a different location... anyway, cvs add is not too bad.
#
def CreateRepositoryFile():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BCreateRepositoryFile@B"
	if not vcs.repositoryDir:
		#print "(CVS) CreateRepositoryFile Warning -- Calling CreateRepositoryDir before CheckIn!"
		if not CreateRepositoryDir():
			return FALSE
	desc = vcs.escape_quotes(vcs.description)
	if not desc: desc = '*** empty description ***'
	# must add in REAL dir!
	dir = CVSCore.real_dir_of_file(vcs.workingDir, vcs.SSWS, vcs.catDir, vcs.workingFile)
	os.chdir(dir)
	cmd = 'cvs -Q add -m "%s" %s' % (desc, vcs.workingFile)
	(rc, out, err) = CVSCore.GetStatusOutput(cmd)
	if not vcs.test(rc): 
		if string.rfind(err, "has already been entered"):
			# this can only happen if CheckRepositoryFile() is not configured to
			# display newly entered files as well.
			print "(CVS) CreateRepositoryFile: file was already entered, no change"
		else:
			print cmd
			print "(CVS) CreateRepositoryFile Error (",rc,"): ",err
			return vcs.test(rc)
	#force re-reading Entries
	CVSCore.get_entries(vcs.workingDir, FALSE)
	#BUGBUG there should be a DoCheckIn() to really do it
	return CheckIn()

#
# test if user is allowed to do all commit operations
# if not, issue an error message and return FALSE.
#
def CheckAllowedUser(theBranch):
	if not CVSPrefs.BranchACL: return TRUE
	if not theBranch:
		theBranch=""
	if CVSPrefs.BranchACL.has_key(theBranch):
		if not vcs.user in CVSPrefs.BranchACL[theBranch]:
			if not theBranch:
				brText = "@BHEAD@B"
			else:
				brText = "Branch @B"+theBranch+"@B"
			print "Sorry ",vcs.user,", you are not allowed to commit to ",brText," !"
			print "Please ask one of ",CVSPrefs.BranchACL[theBranch]," for assistance."
			return FALSE
	return TRUE

#
# CheckIn:
#	checks a file into the repository.
#	CVS checkin happens in 3 stages:
#	(1) tag the branch, (2) commit, (3) tag the version (changeset)
#	CVS custom menues used for that purpose
#
def CheckIn():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BCheckIn@B"
	revArg = ''
	forceArg = ''
	readOnlyArg = ''
	commentArg = '-m"%s"' % vcs.escape_quotes(vcs.comment)
	#BUGBUG it should be disallowed to check-in files from the SSWE unless
	# the CVS/Entries file is writable! One alternative would be to copy -p
	# the file to the PWE and then check it in... but having everything local
	# is probably not what most people want.
	dir = CVSCore.real_dir_of_file(vcs.workingDir, vcs.SSWS, vcs.catDir, vcs.workingFile)
	os.chdir(dir)
	theEntry=CVSCore.GetEntry(dir, vcs.workingFile)
	if not theEntry:
		# CreateRepositoryFile failed or was not executed because there is
		# alredy a version in the repository but no local entry.
		print "(CVS) CheckIn Error: Repository file seems to exist but there is no local entry!"
		print "Please use cvs add, File->CheckOut or InitProjects to create a local Entry."
		return FALSE
	currentVersion = theEntry.getVersion()
	lockedVersion=''

	# print some variables to find out how they are set
	if CVSPrefs.TEST: print "RWE:       ",vcs.repositoryRoot
	if CVSPrefs.TEST: print "SSWE:      ",vcs.repositoryRoot
	if CVSPrefs.TEST: print "RCCS:      ",vcs.RCCSPath
	if CVSPrefs.TEST: print "PWE:       ",vcs.privateRoot
	if CVSPrefs.TEST: print "SSWS:      ",vcs.SSWS
	if CVSPrefs.TEST: print "WorkingDir:",vcs.workingDir
	if CVSPrefs.TEST: print "ProjectDir:",vcs.projectDir
	if CVSPrefs.TEST: print "catDir:    ",vcs.catDir
	if CVSPrefs.TEST: print "realDir:   ",dir
	if CVSPrefs.TEST: print "File:      ",vcs.workingFile, " @ ", currentVersion
	if CVSPrefs.TEST: print "Revision:  ",vcs.revision
	if CVSPrefs.TEST: print "Branch:    ",vcs.branchFlag, ", ", vcs.branchName
	if CVSPrefs.TEST: print "Symbol:    ",vcs.symbolName

	if not theEntry.isNew() and CVSPrefs.RetainLockMode == CVSPrefs.gRetainExIfEx:
		# is the file currently locked by me ?
		log = log_without_history(vcs.workingDir, vcs.workingFile, TRUE)
		locks = string.split(log.lockers())
		for i in range(0,len(locks)-1,2):
			if locks[i][:-1] == vcs.user:
				lockedVersion = locks[i+1]
				if lockedVersion == currentVersion:
					break
				else:
					# with multiple locks, cvs will print an error if committing to 
					# the the same branch where the lock is;
					# else, the lock is removed no matter where it is
					print "WARNING -- exclusively locked different version than local!"
					print "Be prepared for strange things to happen..."
		if CVSPrefs.TEST: print "(CVS) CheckIn: using lockedVersion = ", lockedVersion
	if vcs.branchFlag:
		# remove "SNIFF_BRANCH_"
		branchName = vcs.branchName[13:]
		if not CheckAllowedUser(branchName):
			# CheckAllowedUser already printed an error
			return FALSE
		if theEntry.isNew():
			# if this is a new file which has just been added through
			# CreateRepositoryFile, V is 0. in that case, the new branch
			# can only be created by directly setting the sticky tag.
			# The actual commit command won't have any tag options.
			theEntry.setStickyTag(branchName)
			theEntry.write()
		else:
			revArg = '-r '+branchName
			if vcs.revision:
				# branch from an existing tag
				vcs.system('cvs -Q tag -b -r %s %s %s' % (vcs.revision, branchName, vcs.workingFile))
			else:
				# branch from checked-out version
				vcs.system('cvs -Q tag -b %s %s' % (branchName, vcs.workingFile))
	else:
		# no new branch: revision may give a revision number to check in to
		# but this is not valid at the initial CheckIn
		# BUGBUG it's currently unclear WHEN vcs.revision is really set at CheckIn
		# mostly it is empty! Perhaps only at configurations?
		if vcs.revision and not theEntry.isNew():
			revArg = '-r '+vcs.revision
		theTag = theEntry.getStickyTag()
		if CVSPrefs.TEST: print "(CVS) CheckIn: Security Check: Tag",theTag
		if not CheckAllowedUser(theTag):
			# CheckAllowedUser() already printed an error
			return FALSE
	if vcs.forceFlag:
		forceArg = '-f'
	if vcs.lockType == 'N':
		readOnlyArg = '-r'
	# Repository Dir is already set by CreateRepositoryDir / CreateRepositoryFile
	cmd = 'cvs -Q %s commit %s %s %s %s' % (readOnlyArg, commentArg, forceArg, revArg, vcs.workingFile)
	(rc, stdout, stderr) = CVSCore.GetStatusOutput(cmd)
	# Three possible cases:
	# (1) successful commit -- rc=0 , stdout="Checking in xxx", stderr="SomeStuff"
	# (2) no difference     -- rc=0 , stdout="", stderr=""
	# (3) conflict or error -- rc=1 , stdout="", stderr="SomeStuff"
	if not vcs.test(rc):
		print cmd
		print "(CVS) CheckIn: Commit Error (",rc,"): ",stderr
		# add parameters to conflicts list.
		#CVSCore.AddConflict(vcs.workingDir, vcs.workingFile, currentVersion,
		#	vcs.revision, vcs.branchFlag, vcs.branchName, vcs.symbolName,
		#	vcs.comment, stderr)
	elif not stdout:
		# no difference, commit did nothing
		if CVSPrefs.TEST: print "(CVS) CheckIn: No Difference, commit did nothing!"
		if vcs.lockType == "N":
			vcs.output("File is unchanged, unlocking only")
			if lockedVersion==currentVersion and CVSPrefs.RetainLockMode!=CVSPrefs.gRetainExNever:
				# Be sure to unlock only versions locked by myself!
				vcs.system('cvs admin -u%s %s' % (currentVersion, vcs.workingFile))
			if CVSPrefs.DoCVSEdit:
				#vcs.system('cvs unedit %s' % vcs.workingFile)
				cvsUnedit(dir, vcs.workingFile)
			else:
				vcs.chmod('ugo-w', vcs.workingFile)
	else:
		# successful check-in: force re-loading CVS/Entries because file TimeStamp
		# is not accurate enough!
		#BUGBUG would be better just to invalidate the cache by clearing its timestamp
		CVSCore.get_entries(vcs.workingDir, FALSE)
		if vcs.symbolName:
			cmd = 'cvs tag %s %s ;' % (vcs.symbolName, vcs.workingFile)
		else:
			cmd = ''
		# Finally, do the locking
		if vcs.lockType == 'N':
			if lockedVersion and lockedVersion != currentVersion:
				# lock version on other branch that was accidentally unlocked again
				cmd = cmd + 'cvs admin -l%s %s ; ' % (lockedVersion, vcs.workingFile)
		else:
			# retain lock: retain exclusive or concurrent lock?
			if lockedVersion or (CVSPrefs.RetainLockMode==CVSPrefs.gRetainExAlways):
				# file was exclusively locked -- lock new revision again
				if CVSPrefs.TEST: print "(CVS) CheckIn: Retaining exclusive lock"
				theEntry=CVSCore.GetEntry(dir, vcs.workingFile)
				cmd = cmd + 'cvs admin -l%s %s' % (theEntry.getVersion(), vcs.workingFile)
		rc = vcs.system(cmd)
		if vcs.test(rc) and not vcs.lockType == 'N' and CVSPrefs.DoCVSEdit:
			if CVSPrefs.TEST: print "(CVS) CheckIn: Retaining concurrent lock"
			rc = cvsEdit(vcs.workingDir, vcs.workingFile)
	return vcs.test(rc)


#
# Lock:
#	locks a revision
#
def Lock():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BLock@B"
	if not CVSPrefs.AllowExclusiveLocking:
		CVSDialogs.ErrorDialog("Exclusive Locking has been disabled at your site. Nothing done.")
		return TRUE
	os.chdir(vcs.workingDir)
	vcs.system('cvs admin -q -l%s %s' % (vcs.revision, vcs.workingFile))
	vcs.chmod(vcs.workingFile, 'u+w')
	return TRUE


#
# SetWorkFileReadOnly:
#	sets work file read-only
#
def SetWorkFileReadOnly():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BSetWorkFileReadOnly@B"
	path = vcs.joinpath(vcs.workingDir,vcs.workingFile)
	vcs.chmod(path, 'ugo-w')
	return TRUE


#
# UpdateProject:
#	updates all files of a project
#	not used with CVS
#
def UpdateProject():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BUpdateProject@B"
	return TRUE


#
# UpdateFile:
#	updates a single file
#
def UpdateFile():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BUpdateFile@B"
	PWE = vcs.normpath(vcs.privateRoot)
	SSWE = vcs.SSWE
	if vcs.catDir == vcs.workingDir:
		workfile = abs_wfile = vcs.joinpath(vcs.catDir, vcs.workingFile)
		PWE = SSWE = ""
	else:
		# workfile = vcs.joinpath(vcs.projectDir, vcs.catDir, vcs.workingFile)
		workfile = vcs.joinpath(vcs.projectDir, vcs.workingFile)
		if vcs.workingDir[:len(vcs.projectDir)]==vcs.projectDir:
			if PWE or SSWE:
				print 'Assertion failed: Absolute Project but PWE is set'
				PWE = SSWE = ""
			abs_wfile = workfile
		else:
			abs_wfile = vcs.joinpath(PWE, workfile)
	# if CVSPrefs.TEST: print 'archive:  ', vcs.repositoryFile
	if CVSPrefs.TEST: print 'workfile: ', workfile
	if CVSPrefs.TEST: print 'abs_wfile:', abs_wfile
	if CVSPrefs.TEST: print 'SSWEs:    ', vcs.SSWE
	if CVSPrefs.TEST: print 'revision: ', vcs.revision

	update_writable = 0
	wf_stat = vcs.get_stat(abs_wfile)
	if vcs.test_writable(wf_stat):
		if not CVSPrefs.AllowMergeInSniffUpdate or os.environ.has_key("SNIFF_BATCH"):
			if vcs.SSWE:
				if SYSTEM==UNIX and os.path.islink(abs_wfile):
					return TRUE
				vcs.output('Touching private writable file @B%s@B' % vcs.workingFile)
				vcs.touch(abs_wfile)
			return TRUE
		else:
			# cvs-update of private writable files is allowed.
			# we are in interactive mode.
			# just continue and treat the writable workfile just like a 
			# read-only file.
			if isPDF(vcs.workingFile):
				if vcs.revision:
					revText = "version @B"+vcs.revision+"@B"
					revArg = "-r "+vcs.revision
				else:
					revText = "@BHEAD@B"
					revArg = "-A"
				result = vcs.getoutput('cd %s; cvs -n update %s %s' % (vcs.workingDir, revArg, vcs.workingFile))
				if result[0]=="C":
					vcs.output("\n@rERROR: Conflict detected in trying to update Project Description@x @B"+vcs.workingFile+"@B.\n")
					vcs.output("Please Check out "+revText+" and manually merge the following local changes:\n")
					vcs.output(result)
					# will output all differences to the LogTool!
					vcs.revision2 = vcs.revision
					DiffText_W_V()
					# won't also touch the PDF
					return TRUE
			# ALLOW to cvs-merge writable PDF's if there is no
			# conflict!
			update_writable=1
	else:
		# workfile is not writable
		if SYSTEM==UNIX:
			if vcs.test_mode(wf_stat, 0022):
				vcs.output('Warning: Private file @B%s@B writable by other user' % vcs.workingFile)
				return TRUE

	# workfile is not writable, or cvs-update is allowed for writable files.

	dir = CVSCore.real_dir_of_file(PWE,SSWE,"",workfile)
	theEntry = CVSCore.GetEntry(dir,vcs.workingFile)
	if not theEntry:
		vcs.output("File @B%s@B isn't CVS controlled (no Entry)" % (vcs.workingFile))
		return FALSE
	if vcs.joinpath(dir, vcs.workingFile) == abs_wfile:
		theVersion = theEntry.getVersion()
		# treate added/removed but uncommited files same as writable files
		if theVersion[0] == "0" and not CVSPrefs.AllowMergeInSniffUpdate: 
			vcs.output('File @B%s@B was freshly added but not yet committed - treated as writable' % vcs.workingFile)
			return FALSE
		elif theVersion[0] == "-" and not CVSPrefs.AllowMergeInSniffUpdate:
			vcs.output('File @B%s@B was removed but not yet committed - treated as writable' % vcs.workingFile)
			return FALSE


	if not vcs.SSWE:
		# project not shared
		#rc = vcs.system('cd %s; %s %s> %s' % (PWE, DIFF_CMD, workfile, NULLDEV))
		rc = cvsIsDifferent(PWE, workfile, vcs.revision)
		if rc == 1:
			vcs.output('-> Checking @B%s@B out' % vcs.workingFile)
			if CVSPrefs.AllowMergeInSniffUpdate:
				# BUGBUG would be more efficient by not doing the cvs diff...
				res = cvsUpdate(PWE, workfile, vcs.revision, 0, update_writable)
				if res: vcs.output(NL + res)
			else:
				res = cvsUpdate(PWE, workfile, vcs.revision, 1)
			CVSCore.get_entries(vcs.workingDir, FALSE)
			return TRUE
		elif rc == 2:
			return FALSE
		else:
			# BUGBUG
			# Files are identical but probably a different version was requested.
			# This different version should be reflected in CVS/Entries!
			# if theEntry.getVersion() != vcs.revision:
			return TRUE

	# project is shared

	if CVSPrefs.TEST: print 'Project is shared'
	SSWElist = string.split(vcs.SSWE, DELIMITER)
	if CVSPrefs.TEST: print 'SSWElist:', SSWElist
	if not vcs.test_exists(wf_stat):
		if CVSPrefs.TEST: print 'Private file does not exist'
		shared_file = ''
		for SSWE in SSWElist:
			shared_file = workfile
			if os.path.exists(os.path.expandvars(vcs.joinpath(SSWE,shared_file))):
				break
			shared_file = ''
		if not shared_file:
			# shared file does not exist
			if CVSPrefs.TEST: print 'Shared file does not exist'
			vcs.output('-> Checking @B%s@B out' % vcs.workingFile)
			# BUGBUG a cvs update might be faster
			res = cvsUpdate(PWE, workfile, vcs.revision, 1)
			CVSCore.get_entries(vcs.workingDir, FALSE)
			return TRUE
		# shared file exists
		#rc = vcs.system('cd %s; %s %s > %s' % (SSWE, DIFF_CMD, shared_file, NULLDEV))
		rc = cvsIsDifferent(SSWE, shared_file, vcs.revision)
		if rc == 1:
			if CVSPrefs.TEST: print 'Shared file is not up-to-date'
			vcs.output('-> Checking @B%s@B out' % vcs.workingFile)
			# BUGBUG a cvs update might be faster
			res = cvsUpdate(PWE, workfile, vcs.revision, 1)
			CVSCore.get_entries(vcs.workingDir, FALSE)
			return TRUE
		elif rc == 2:
			return FALSE
		if CVSPrefs.TEST: print 'Shared file is up-to-date'
		return TRUE

	# file exists in local WS

	if CVSPrefs.TEST: print 'Private file exists'
	shared_file = ''
	for SSWE in SSWElist:
		shared_file = workfile
		debFile = os.path.expandvars(vcs.joinpath(SSWE,shared_file))
		if CVSPrefs.TEST: print "Checking",  debFile
		sf_stat = vcs.get_stat(debFile)
		if vcs.test_exists(sf_stat):
			if CVSPrefs.TEST: print 'File exists'
			if SYSTEM==WINDOWS or not os.path.islink(debFile):
				# we found the real shared file
				break
		shared_file = ''
	if not shared_file:
		# shared file does not exist
		if CVSPrefs.TEST: print 'Shared file does not exist'
		#rc = vcs.system('cd %s; %s %s > %s' % (PWE, DIFF_CMD, workfile, NULLDEV))
		rc = cvsIsDifferent(PWE, workfile, vcs.revision)
		if rc == 1:
			vcs.output('-> Checking @B%s@B out' % vcs.workingFile)
			if CVSPrefs.AllowMergeInSniffUpdate:
				# BUGBUG would be more efficient by not doing the cvs diff...
				res = cvsUpdate(PWE, workfile, vcs.revision, 0, update_writable)
				if res: vcs.output(NL + res)
			else:
				res = cvsUpdate(PWE, workfile, vcs.revision, 1)
			CVSCore.get_entries(vcs.workingDir, FALSE)
			return TRUE
		elif rc == 2:
			return FALSE
		return TRUE

	# shared file exists

	if CVSPrefs.TEST: print 'Shared file:', vcs.joinpath(SSWE,shared_file)
	#rc = vcs.system('cd %s; %s %s > %s' % (SSWE, DIFF_CMD, shared_file, NULLDEV))
	rc = cvsIsDifferent(SSWE, shared_file, vcs.revision)
	if rc == 1:
		# shared file is not up-to-date
		if CVSPrefs.TEST: print 'Shared file is not up-to-date'
		#rc = vcs.system('cd %s; %s %s > %s' % (PWE, DIFF_CMD, workfile, NULLDEV))
		rc = cvsIsDifferent(PWE, workfile, vcs.revision)
		if rc == 1:
			vcs.output('-> Checking @B%s@B out' % vcs.workingFile)
			if CVSPrefs.AllowMergeInSniffUpdate:
				# BUGBUG would be more efficient by not doing the cvs diff...
				res = cvsUpdate(PWE, workfile, vcs.revision, 0, update_writable)
				if res: vcs.output(NL + res)
			else:
				res = cvsUpdate(PWE, workfile, vcs.revision, 1)
			CVSCore.get_entries(vcs.workingDir, FALSE)
			return TRUE
		elif rc == 2:
			return FALSE
		if SYSTEM==UNIX and os.path.islink(abs_wfile):
			return TRUE
		# private file is up-to-date but not shared file
		if CVSPrefs.TEST: print 'Private file is up-to-date'
		if update_writable:
			vcs.output('Touching private writable file @B%s@B' % vcs.workingFile)
		else:
			vcs.output('Touching private read-only file @B%s@B' % vcs.workingFile)
		vcs.touch(abs_wfile)
		return TRUE
	elif rc == 2:
		return FALSE

	#shared file is up-to-date - remove local

	if CVSPrefs.TEST: print 'Shared file is up-to-date'
	if update_writable:
		res = cvsUpdate(PWE, workfile, vcs.revision, 0, update_writable)
		if res: vcs.output(NL + res)
		# NEVER remove a writable file.
		return TRUE
	rc = vcs.rm_f(abs_wfile)
	if rc == 0:
		CVSCore.RemoveEntry(abs_wfile)
		vcs.output('Private file @B%s@B is deleted, shared file is used' % vcs.workingFile)
		return TRUE
	vcs.output('Private file @B%s@B cannot be deleted' % vcs.workingFile)
	return FALSE

#
# Unlock:
#	unlocks a revision
#
def Unlock():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x Unlock"
	if not CVSPrefs.AllowExclusiveLocking:
		CVSDialogs.ErrorDialog("Exclusive Locking has been disabled at your site. Nothing done.")
		return TRUE
	os.chdir(vcs.workingDir)
	rc = vcs.system('cvs admin -u%s %s' % (vcs.revision, vcs.workingFile))
	return vcs.test(rc)


#
# BreakLock:
#	breaks lock on  a revision
#
def BreakLock():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BBreakLock@B"
	(method, path) = CVSCore.split_path(vcs.repositoryFile)
	if method == "local":
		comm = vcs.user+' broke the lock with SNiFF+.'
		(origMethod, dummy) = CVSCore.split_path(vcs.repositoryDir)
		if origMethod == "local":
			if CVSPrefs.TEST: print "(CVS) BreakLock: local, using cvs command"
			os.chdir(vcs.workingDir)
			args = (comm, vcs.revision, vcs.workingFile)
			rc = vcs.sh('( echo y; echo %s; echo . ) | cvs admin -I -u%s %s' % args)
		else:
			# use rcs because of repository mapping from remote.
			# BUGBUG doesn't work if the file is in the Attic.
			if CVSPrefs.TEST: print "(CVS) BreakLock: translation, using rcs command"
			args = (comm, vcs.revision, vcs.repositoryFile)
			rc = vcs.sh('( echo y; echo %s ) | rcs -V5 -I -u%s %s' % args)
		return vcs.test(rc)
	else:
		print "BreakLock() on ", vcs.workingFile, ":"
		print "Breaking a lock is only possible if the repository is local."
		print "Please try the following command in your shell:"
		print "rsh <yourHost> rcs -u%s %s" % (vcs.revision, path)
	return TRUE


#
# DeleteVersion:
#	deletes a revision
#
def DeleteVersion():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BDeleteVersion@B"
	if CVSPrefs.AdminACL and not vcs.user in CVSPrefs.AdminACL:
		msg = "Sorry " + vcs.user + ", delete version is forbidden for you." + NL
		msg = msg + "Please contact one of " + CVSPrefs.AdminACL + " !"
		CVSDialogs.ErrorDialog(msg)
		return TRUE
#		print "Sorry ", vcs.user, ", delete version is forbidden for you."
#		print "Please contact one of ", CVSPrefs.DeleteAllowedUsers, " !"
#		return FALSE
	else:
		os.chdir(vcs.workingDir)
		rc = vcs.system('cvs admin -q -o%s %s' % (vcs.revision, vcs.workingFile))
		return vcs.test(rc)


#
# SetDescription:
#	adds or replaces description of a file
#
def SetDescription():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BSetDescription@B"
	rc = vcs.system('cd %s; cvs admin -q -t-"%s" %s' % (vcs.workingDir, vcs.description, vcs.workingFile))
	return vcs.test(rc)


#
# SetSymbol:
#	adds a symbolic name to a version
#	Needed for freeze in the config manager
#	CVS custom menues are quicker for that purpose
#
def SetSymbol():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BSetSymbol@B ("
	if CVSPrefs.TEST: print vcs.workingFile, ", ", vcs.symbolName," : ",vcs.revision,")"
	theSymbol = vcs.symbolName
	if theSymbol[:13] == "SNIFF_BRANCH_":
		# with CVS-1.9 on HP-UX you can create branchtags even if the corresponding revisions
		# don't exist!
		if CVSPrefs.AllowBranchOpsInCM:
			theSymbol = theSymbol[13:]
			# convert 1.1.2.1 to 1.1.0.2
			revList = string.split(vcs.revision,".")
			revList[-1] = revList[-2]
			revList[-2] = "0"
			theRevision = string.join(revList, ".")
			if vcs.forceFlag:
				forceOpt = "-N"
			else:
				forceOpt = "-n"
			## BUGBUG if rcs is available, should use rcs -- works with cvs1.10 as well
			cmd = 'cvs admin %s%s:%s %s' % (forceOpt, theSymbol, theRevision, vcs.workingFile)
			rc = vcs.system(cmd)
			return vcs.test(rc)
		else:
			# with CVS-1.10, you cannot set a branchtag like "1.2.0.1" because this tag should be set
			# on a non-existing version. You also cannot set tags on "1.2.1" -- neither with
			# cvs tag nor with cvs admin.
			# You also cannot create a branchtag for an existing, specific revision - cvs tag -b
			# will always create a NEW branch number based on the revision given.
			# Therefore, just disable creating branchnames from the Config Manager.
			print "You cannot create a CVS Branch names with the Config Manager."
			print "Please use the 'cvs tag' or 'cvs rtag' command to create branch names."
			return FALSE

	elif len(theSymbol) < 16 or theSymbol[:16] != "SNIFF_CHANGESET_":
		# Not setting a branch symbol and not setting a Changeset symbol:
		# make sure that the tag requested does not already exist as a branch tag!
		# look for the symbol as a branch name!
		result = cvsRevisionOfSymbol(vcs.workingDir, vcs.workingFile, "SNIFF_BRANCH_"+theSymbol)
		if result:
			# ouch, it exists as a branch!
			# CVSDialogs.ErrorDialog()
			print "(SetSymbol) Error: Symbol ",theSymbol, " exists already as a branch name, cannot set the symbol!"
			return FALSE
		# look for the symbol as a branch name!
		result = cvsRevisionOfSymbol(vcs.workingDir, vcs.workingFile, "SNIFF_BRANCH_"+theSymbol)
		if result:
			# ouch, it exists as a branch!
			# CVSDialogs.ErrorDialog()
			print "(SetSymbol) Error: Symbol ",theSymbol, " exists already as a branch name, cannot set the symbol!"
			return FALSE

	if CVSPrefs.TagACL and CVSPrefs.TagACL.has_key(theSymbol) and not vcs.user in CVSPrefs.TagACL[theSymbol]:
		msg = "Sorry " + vcs.user + ", you are not allowed to create the symbol " + theSymbol + NL
		msg = msg + "Please contact one of " + CVSPrefs.TagACL[theSymbol] + " !"
		CVSDialogs.ErrorDialog(msg)
		# Error has already been shown, so return TRUE.
		# return FALSE
		return TRUE

	# Here, tag doesn't really need to read the Entries file because
	# we supply all information; but it may produce an ugly error message
	dir = CVSCore.real_dir_of_file(vcs.workingDir, vcs.SSWS, vcs.catDir, vcs.workingFile)
	os.chdir(dir)
	if vcs.forceFlag: forceOpt = '-F'
	else: forceOpt = ''
	cmd = 'cvs tag %s -r %s "%s" %s' % (forceOpt, vcs.revision, vcs.symbolName, vcs.workingFile)
	rc = vcs.system(cmd)
	return vcs.test(rc)


#
# DeleteSymbol:
#	deletes a symbolic name from a version
#	cvs rtag -d [module] is quicker
#
def DeleteSymbol():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BDeleteSymbol@B ("
	if CVSPrefs.TEST: print vcs.workingFile, ", ", vcs.symbolName, ")"
	theSymbol = vcs.symbolName
	if theSymbol[:13] == "SNIFF_BRANCH_":
		if not CVSPrefs.AllowBranchOpsInCM:
			print "You cannot delete CVS Branch names with the Config Manager."
			print "Please use the 'cvs tag' or 'cvs rtag' command to delete branch names."
			return FALSE
		# Continue...
		theSymbol = theSymbol[13:]
	if CVSPrefs.AdminACL and not vcs.user in CVSPrefs.AdminACL:
		msg = "Sorry " + vcs.user + ", you are not allowed to delete the symbol " + theSymbol + NL
		msg = msg + "Please contact one of " + CVSPrefs.AdminACL + " !"
		CVSDialogs.ErrorDialog(msg)
		return FALSE
	if CVSPrefs.TagACL and CVSPrefs.TagACL.has_key(theSymbol) and not vcs.user in CVSPrefs.TagACL[theSymbol]:
		msg = "Sorry " + vcs.user + ", you are not allowed to delete the symbol " + theSymbol + NL
		msg = msg + "Please contact one of " + CVSPrefs.TagACL[theSymbol] + " !"
		CVSDialogs.ErrorDialog(msg)
		return FALSE
	dir = CVSCore.real_dir_of_file(vcs.workingDir, vcs.SSWS, vcs.catDir, vcs.workingFile)
	os.chdir(dir)
	cmd = 'cvs tag -d "%s" %s' % (theSymbol, vcs.workingFile)
	vcs.system(cmd)
	return TRUE


#
# ChangeComment:
#	changes comment on given revision
#
def ChangeComment():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BChangeComment@B"
	cmd = 'cd %s; cvs admin -q -m%s:"%s" %s' % (vcs.workingDir, vcs.revision, vcs.comment, vcs.workingFile)
	vcs.system(cmd)
	return TRUE


#
# Description:
#	get description of a file ( used in the history window )
#
def Description():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BDescription@B"
	log = log_with_history(vcs.workingDir, vcs.workingFile, TRUE)
	if not log:
		return FALSE
	vcs.output(log.description)
	return TRUE


#
# History:
#	get history of a file ( used in the history window )
#
def History():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BHistory@B"
	log = log_with_history(vcs.workingDir, vcs.workingFile, FALSE)
	if not log:
		if CVSPrefs.TEST: print "(CVS) History: log() is FALSE"
		return FALSE
	vcs.output(log.history_str())
	if CVSPrefs.TEST: print log.history_str()
	return TRUE


#
# Head:
#	get history without comment ( used in the history window )
#
def Head():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x Head"
	log = log_without_history(vcs.workingDir, vcs.workingFile, TRUE)
	if not log:
		return FALSE
	vcs.output(log.head_rev())
	return TRUE


#
# SymbolList:
#	get list of revisions and symbolic names
#	used in the history window (?) and when the ConfigManager does "Update"
#	in the latter case, we get a LIST OF FILES!
#
def SymbolList():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x SymbolList(", vcs.workingFile,")"
	log = log_with_history(vcs.workingDir, vcs.workingFile, FALSE)
	if not log:
		return FALSE
	out_list = []
	while log:
		# BUGBUG Lockers info seems to be new with SNiFF 3.1 only!
		# out_list.append('\014%s\t%s' % (log.work_file(), log.lockers()))
		if CVSPrefs.TEST2: print "----SymbolList: ",log.work_file()
		out_list.append('\014%s' % (log.work_file()))
		out_list.append('HEAD: %s' % log.head_rev())
		symbols = log.symbolstr()
		if CVSPrefs.TEST2: print symbols
		if symbols:
			out_list.append(symbols)
		for rev in log.revision_list():
			out_list.append(string.join((rev.rev, rev.date, rev.author, rev.lines)))
		log.next()
	vcs.output(string.join(out_list, NL))
	if CVSPrefs.TEST: print string.join(out_list,NL)
	return TRUE


#
# RevisionOfSymbol:
#	prints revision belonging to a symbolic name
#
def RevisionOfSymbol():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BRevisionOfSymbol@B"
	result = cvsRevisionOfSymbol(vcs.workingDir, vcs.workingFile, vcs.symbolName)
	if result:
		vcs.output(result)
		return TRUE
	else:
		return FALSE

#
# HistoryOfRevision:
#	prints history of revision
#
def HistoryOfRevision():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BHistoryOfRevision@B"
	log = log_with_history(vcs.workingDir, vcs.workingFile, TRUE)
	if not log:
		return FALSE
	rev = log.revision(vcs.revision)
	if not rev:
		return FALSE
	vcs.output(str(rev))
	return TRUE


#
# Comment:
#	prints comment of revision
#
def Comment():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BComment@B"
	log = log_with_history(vcs.workingDir, vcs.workingFile, TRUE)
	if not log:
		return FALSE
	rev = log.revision(vcs.revision)
	if not rev:
		return FALSE
	vcs.output(rev.log_message)
	return TRUE


#
# Lockers:
#	print list of lockers
#	This can be customized to either print the local version and sticky tag
#	or print real exclusive lockers
#
def Lockers():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BLockers@B"
	# this is the only place where a log is done for multiple files
	# so typically, the previous log cannot be re-used
	out_list = []
	processed = {}
	if CVSPrefs.LockersShowExclusive:
		log = log_without_history(vcs.workingDir, vcs.workingFile, FALSE)
		while log:
			processed[log.work_file()] = log.lockers()
			log.next()
	wfiles = string.split(vcs.workingFile)
	for wfile in wfiles:
		result = wfile
		if processed.has_key(wfile):
			result = result + " " + processed[wfile]
		if CVSPrefs.LockersShowEntries:
			dir = CVSCore.real_dir_of_file(vcs.workingDir, vcs.SSWS, vcs.catDir, vcs.workingFile)
			theEntry = CVSCore.GetEntry(dir, vcs.workingFile)
			if theEntry:
				result = result + " " + theEntry.modified() + ": " + theEntry.getVersion()
				if theEntry.getStickyTag():
					# result = result + "-T" + theEntry.getStickyTag()
					result = result + "-" + theEntry.getStickyTag()
		out_list.append(result)
	result = string.join(out_list, NL) + NL
	if CVSPrefs.TEST: print "(CVS) Lockers for: ", vcs.workingDir
	if CVSPrefs.TEST: print "(CVS) Lockers are: ", result
	vcs.output(result)
	return TRUE


#
# IsLockedBy:
#	prints wheter a repository file is locked by a specific user
#
def IsLockedBy():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BIsLockedBy@B"
	log = log_without_history(vcs.workingDir, vcs.workingFile, TRUE)
	if not log: return FALSE
	nLocks = string.find(log.lockers(), vcs.User)
	if CVSPrefs.TEST: print "--> ",nLocks
	return nLocks >= 0


#
# IsLocked:
#	prints wheter a repository file is locked by a any user
#
def IsLocked():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BIsLocked@B"
	log = log_without_history(vcs.workingDir, vcs.workingFile, TRUE)
	if not log: return FALSE
	nLocks = len(log.lockers())
	if CVSPrefs.TEST: print "--> ",nLocks
	return nLocks > 0


#
# IsDifferent:
#	differs file against repository?
#
def IsDifferent():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BIsDifferent@B ( ",
	if CVSPrefs.TEST: print vcs.workingFile, ", ", vcs.revision, ")"
	if not vcs.revision:
		if CVSPrefs.FastModified:
			rev = "BASE"
		else:
			# need to compare against REAL head!
			if CVSPrefs.TEST: print "(CVS) IsDifferent: obtaining Head..."
			log = log_without_history(vcs.workingDir, vcs.workingFile, TRUE)
			rev = log.head_rev()
			# diffs against the head of branch unless any sticky tags are removed before
			# rev = "HEAD"
	else:
		rev = vcs.revision
	dir = CVSCore.real_dir_of_file(vcs.workingDir, vcs.SSWS, vcs.catDir, vcs.workingFile)
	theEntry = CVSCore.GetEntry(dir, vcs.workingFile)
	if not theEntry: return TRUE
	if rev == "BASE" or rev == theEntry.getVersion():
		if CVSPrefs.TEST: print "(CVS) IsDifferent: using Entry!"
		return not theEntry.isModified()
	else:
		if CVSPrefs.TEST: print "(CVS) IsDifferent: running diff against ", rev
		os.chdir(dir)
		try:
			[root, relative] = string.split(vcs.repositoryDir,DELIMITER)
		except:
			print "(CVS) IsDifferent: ERROR - Repository Dir of (",
			print vcs.workingDir, ", ", vcs.workingFile,") is illegal: ", vcs.repositoryDir
			return FALSE
		rc = vcs.system('cvs -n -d %s diff -N -h -kkvl -r %s -b %s' % (root, rev, vcs.workingFile))
	return vcs.test(rc)


#
# DiffText_W_V:
#	show differences between workfile and a specific revision: revision2 is passed!
#
def DiffText_W_V():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x DiffText_W_V ("
	if CVSPrefs.TEST: print vcs.workingFile,", ",vcs.revision2,")"
	if vcs.revision2:
		revArg = "-r"+vcs.revision2
	else:
		log = log_without_history(vcs.workingDir, vcs.workingFile, TRUE)
		revArg = "-r"+log.head_rev()
		# -r HEAD compares against the HEAD of current branch with sticky tags!
	# We need to use CheckOut() because cvs diff's output is the wrong way round!
	# Advantage: don't need local CVS/Entries file.
	try:
		[root, relative] = string.split(vcs.repositoryDir,DELIMITER)
	except:
		print "(CVS) DiffText_W_V: ERROR - Repository Dir of (",
		print vcs.workingDir, ", ", vcs.workingFile,") is illegal: ", vcs.repositoryDir
		return FALSE
	workFile = vcs.joinpath(vcs.workingDir, vcs.catDir, vcs.workingFile)
	remoteFile = vcs.joinpath(relative, vcs.workingFile)
	localFile = vcs.tmpFile
	rc = vcs.system('cvs -d %s co -p %s %s > %s' % (root, revArg, remoteFile, vcs.tmpFile))
	out = vcs.getoutput('diff -b %s %s' % (workFile, localFile))
	vcs.rm_fr(vcs.tmpFile)
	vcs.output(out)
#	return out != ''
	return out == ''


#
# DiffText_V_V:
#	shows differences between two versions of a file
#	uses CheckOut()
#
def DiffText_V_V():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x DiffText_V_V"
	if CVSPrefs.TEST: print "projectDir:", vcs.projectDir, ", catDir:",vcs.catDir,", revision:",vcs.revision, ", revision2:", vcs.revision2
	if CVSPrefs.TEST: print "workingDir:", vcs.workingDir, ", SSWS:",vcs.SSWS, ", SSWE:", vcs.SSWE
	#
	# BUG in SNiFF 3.0.2: first, vcs.catDir is EMPTY
	# Second, vcs.revision2 is always EMPTY
	# Bugfix for AMADEUS: with SNiFF 3.0.2, the catDir is always empty
	# try to find the file
	if not vcs.catDir and not vcs.SSWS and not vcs.SSWE and not vcs.revision2:
		# these two lines are bugfix!
		vcs.output("files-are-different")
		return TRUE
		#for catDirTry in [ "", "C/", "Include/", "Asm/", "Ftn/", "desc/", "Trace/C/", "Trace/Include/", "Xdr/C/", "Xdr/Include/", "others/", "lnk/", "pcf/", "sniffdoc/" ]:
		#	testPath = vcs.joinpath(vcs.workingDir, catDirTry, vcs.workingFile)
		#	st = vcs.get_stat(testPath)
		#	if vcs.test_exists(st):
		#		catDir = catDirTry
		#		break
		#else:
		#	print "(CVS) DiffText_V_V: Cannot Determine FileType Directory of ",vcs.workingFile," (SNiFF 302 Bug Workaround)"
		#	print "(CVS) DiffText_V_V: Please Check out Files before trying to merge!"
		#	vcs.output("files-are-different")
		#	return TRUE
	else:
		catDir = vcs.catDir
	# this should now work with 3.1 again
	#workingDir = vcs.joinpath(vcs.workingDir, catDir)
	workingDir = vcs.workingDir
	## PROBLEM: if one of the revisions is "dead" because the file was removed
	## or it was initially added on a branch, cvs diff produces an error.
	## Solution: Checkout to stdout and use local diff for the comparison.
	## Second advantage of this solution - we don't need CVS/Entries or real_dir_of_file
	# guess with cvs diff -N this is no longer true...
	if vcs.revision:
		revArg = "-r"+vcs.revision
	else:
		log = log_without_history(workingDir, vcs.workingFile, TRUE)
		revArg = "-r"+log.head_rev()
		if CVSPrefs.TEST: print "Substituting empty rev1 with ",revArg
		# -r HEAD compares against the HEAD of current branch with sticky tags!
	if vcs.revision2:
		revArg2 = "-r"+vcs.revision2
	else:
		log = log_without_history(workingDir, vcs.workingFile, TRUE)
		revArg2 = "-r"+log.head_rev()
		if CVSPrefs.TEST: print "Substituting empty rev2 with ",revArg2
		# -r HEAD compares against the HEAD of current branch with sticky tags!
	# Need the relative path in the repository
	try:
		[root, relative] = string.split(vcs.repositoryDir,DELIMITER)
	except:
		print "(CVS) DiffText_V_V: ERROR - Repository Dir of (",
		print workingDir, ", ", vcs.workingFile,") is illegal: ", vcs.repositoryDir
		return FALSE
	remoteFile = vcs.joinpath(relative, vcs.workingFile)
	rc = vcs.system('cvs -d %s co -p %s %s > %s' % (root, revArg, remoteFile, vcs.tmpFile))
	if not vcs.test(rc):
		print "Error Checking out ",remoteFile," revision1: ",revArg
		return FALSE
	rc = vcs.system('cvs -d %s co -p %s %s > %s' % (root, revArg2, remoteFile, vcs.tmpFile+"2"))
	if not vcs.test(rc):
		print "Error Checking out ",remoteFile," revision2: ",revArg
		vcs.rm_fr(vcs.tmpFile)
		return FALSE
	out = vcs.getoutput('diff -b %s %s' % (vcs.tmpFile, vcs.tmpFile+"2"))
	vcs.rm_fr(vcs.tmpFile)
	vcs.rm_fr(vcs.tmpFile+"2")
	vcs.output(out)
	return out == ''


#
# RepositoryDir:
#	defines repository directory name
#	Result is "CVSRoot DELIMITER relativeRepositoryDir" (DELIMITER is usually ";")
#	CVS-Stored Root / Repository information has always precedence!
#	The RCCSDir is never used by CVS.
#
def RepositoryDir():
	if CVSPrefs.TEST2: print "(CVS) @r>>>@x @BRepositoryDir@B (RCCSPath: ",vcs.RCCSPath, ", catDir: ", vcs.catDir, ")"
	#if CVSPrefs.TEST2: print "(CVS)      RepositoryDir: workingDir = ", vcs.workingDir, ", workingFile = ", vcs.workingFile
	#if CVSPrefs.TEST: print_vars()
	Rep = CVSCore.find_repository(vcs.workingDir, vcs.SSWS, vcs.catDir)
	if CVSPrefs.TEST2: print "(CVS) @g<<<@x RepositoryDir result: ", Rep
	vcs.output(Rep)
	return TRUE


#
# RepositoryFile:
#	defines repository file name
#	Will only be set if it is readable locally
#	Must not be set to empty if we expect a Repository: all files that are set
#	to Empty will get same Result of CheckRepositoryFile()!
#
def RepositoryFile():
	if CVSPrefs.TEST2: print "(CVS) @r>>>@x @BRepositoryFile@B (",
	if CVSPrefs.TEST2: print "repositoryDir: ", vcs.repositoryDir, ", workingFile: ", vcs.workingFile, ")"
	result = ""
	if not vcs.repositoryDir:
		if CVSPrefs.TEST2: print "(CVS) RepositoryFile: no repository"
		# try to output some kind of RepositoryFile, because the log caching
		# mechanism seems to need it.
		#result = vcs.joinpath(vcs.repositoryRoot, vcs.projectDir, vcs.catDir, vcs.workingFile + ",v")
		if vcs.RCCSPath[:len(vcs.repositoryRoot)] == vcs.repositoryRoot:
			realRCCSPath = vcs.RCCSPath[len(vcs.repositoryRoot):]
			if len(realRCCSPath) > 4 and realRCCSPath[1] == ":":
				# it was falsely interpreted as relative
				vcs.RCCSPath = realRCCSPath
		result = vcs.joinpath(vcs.RCCSPath, vcs.catDir, vcs.workingFile + ",v")
	else:
		try:
			[ cvsroot, relative ] = string.split(vcs.repositoryDir, DELIMITER)
		except:
			print "ERROR string.split ", vcs.repositoryDir, " on DELIMITER ", DELIMITER
		# look up cvsroot if there is a local translation
		cvsroot = CVSPrefs.TranslateRoot(vcs.repositoryRoot, cvsroot)
		# this is not really correct because the file may be in the Attic as well.
		result = vcs.joinpath(cvsroot, relative, vcs.workingFile + ',v')
	if CVSPrefs.TEST2: print "(CVS) @g<<<@x RepositoryFile : ",result
	vcs.output(result)
	return TRUE


#
# TempDir:
#	defines temporary directory name
#
def TempDir():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x TempDir"
	vcs.output(vcs.joinpath(vcs.OSTempDir, '.sniffdir2', 'CVS_%s%s' % (vcs.user, vcs.pid)))
	return TRUE


#
# TempFile:
#	defines temporary file name
#
def TempFile():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x TempFile"
	vcs.output(vcs.joinpath(vcs.tmpDir, vcs.workingFile))
	return TRUE


#
# BranchDef:
#	defines if a new branch is generated at ci or co time
#
def BranchDef():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BBranchDef@B"
	vcs.output('I')
	return TRUE


#
# RepositoryCheckDef:
#	defines if repository information should be processed internally or externally
#	SNiFF must not check!
#
def RepositoryCheckDef():
	vcs.output('E')
	return TRUE


#
# CheckRepositoryDir:
#	depends on RepositoryCheckDef() being set to "E"
#	In CVS, a Repository dir always "exists" since we do not want the ugly
#	SNiFF message saying "Repository dir of... does not exist / create it?"
#	This dialog's functionality will be implemented internally.
#
def CheckRepositoryDir():
	# if CVSPrefs.TEST: print "(CVS) @r>>>@x @BCheckRepositoryDir@B"
	return TRUE


#
# CheckRepositoryFile:
#	depends on RepositoryCheckDef() being set to "E"
#	In CVS, a Repository file is said to exist iff there is an Entry for it.
#	
def CheckRepositoryFile():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BCheckRepositoryFile@B (",
	if CVSPrefs.TEST: print vcs.catDir, " / ", vcs.workingFile, " : ", vcs.repositoryFile, ")"
	dir = CVSCore.real_dir_of_file(vcs.workingDir, vcs.SSWS, vcs.catDir, vcs.workingFile)
	theEntry=CVSCore.GetEntry(dir, vcs.workingFile)
	if not theEntry or theEntry.isNew():
		# Give it a chance for INITIAL check out...
		method, path = CVSCore.split_path(vcs.repositoryFile)
		if method=='local':
			# BUGBUG this way of handling things makes it necessary that the CORRECT
			# repository Dir is already known here...
			st = vcs.get_stat(path)
			if vcs.test_exists(st):
				if CVSPrefs.TEST: print "(CVS) CheckRepositoryFile: found new file in local rep."
				return TRUE
			dir, file = os.path.split(path)
			st = vcs.get_stat(vcs.joinpath(dir,"Attic",file))
			if vcs.test_exists(st):
				if CVSPrefs.TEST: print "(CVS) CheckRepositoryFile: found new file in local Attic"
				return TRUE
		else:
			# remote repository
			localFile = vcs.joinpath(vcs.workingDir, vcs.workingFile)
			st = vcs.get_stat(localFile)
			if not vcs.test_exists(st):
				if CVSPrefs.TEST: print "(CVS) @r<<<@x CheckRepositoryFile: no workFile, treat as TRUE for CheckOut"
				return TRUE
		if not theEntry or theEntry.isNew():
			if CVSPrefs.TEST: print "(CVS) @r<<<@x CheckRepositoryFile: File is new local, not yet in Repository."
			# BUGBUG perhaps better return FALSE... might be interesting to see freshly
			# added files that are not yet version controlled as well... a matter of
			# configuration?
			# if set to TRUE, all difftext, log handling etc. functions must be 
			# prepared to handle non-existent Repositories!
			return FALSE
		else:
			if CVSPrefs.TEST: print "(CVS) @r<<<@x CheckRepositoryFile doesn't exist."
			return FALSE
	else:
		if CVSPrefs.TEST: print "(CVS) @r<<<@x CheckRepositoryFile exists."
		return TRUE


#
# DateOfRepositoryFile:
#	depends on RepositoryCheckDef()
#
def DateOfRepositoryFile():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BDateOfRepositoryFile@B"
	method, path = CVSCore.split_path(vcs.repositoryFile)
	if method == 'local':
		st = vcs.get_stat(path)
		if not st:
			#print "Error -- Repository File should be there due to Entry but not found"
			#can happen if Repository is in Attic or file was newly added
			mtime = time.time() # treat as remote
		else:
			mtime = vcs.get_mtime(st)
	else:
		mtime = time.time()	# remote repositories are never the same
	vcs.output(time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(mtime)))
	return TRUE


#
# CreateRepositoryDir:
#	creates repository directory if not available
#	In CVS, Repository Directories can only be created with cvs import.
#
def CreateRepositoryDir():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BCreateRepositoryDir@B"
	# if multiple files are checked in at once, it is possible that the
	# correct repository dir is alredy known!
	Rep = CVSCore.find_repository(vcs.workingDir, vcs.SSWS, vcs.catDir)
	if not Rep:
		# we must find the next CVS controlled directory;
		# ask the user about correct repository dir;
		# create the repository dir either with a series of cvs add or with cvs import;
		relative = CVSCore.relative_dir(vcs.workingDir, vcs.projectDir, vcs.catDir)
		#BUGBUG what about directories like /../../xyz?
		#BUGBUG should we go to the real_dir_of_file?
		(parent, child) = os.path.split(vcs.workingDir)
		parentRep = CVSCore.get_repository(parent)
		if parentRep:
			[ parentRoot, parentRelative ] = string.split(parentRep, vcs.DELIMITER)
			Rep = CVSDialogs.GuessRepositoryDialog(vcs.repositoryRoot, relative, vcs.joinpath(parentRelative,child), parentRoot)
			# if result == vcs.joinpath(parentRelative,child):
			#	cvs add 
		else:
			Rep = CVSDialogs.GuessRepositoryDialog(vcs.repositoryRoot, relative)
		if not Rep:
			# BUGBUG this will kill only ONE of the dialogs, but still it will
			# continue to pop up for other files! How to avoid this?
			return FALSE
		(root, relative) = Rep
	else:
		[ root, relative ] = string.split(Rep, vcs.DELIMITER)
	# print "(CVS) CreateRepositoryDir: About to create (", root, ", ", relative, ")"
	# the import may happen anywhere!
	# os.chdir(vcs.workingDir)
	cmd = 'cvs -Q -d %s import -I "*" -m "create Directory" %s sniff start' % (root, relative)
	(rc, out, err) = CVSCore.GetStatusOutput(cmd)
	if vcs.test(rc):
		CVSCore.write_repository(vcs.workingDir, root, relative)
		if parentRep:
			CVSCore.MakeDirEntry(parent,child).write()
	else:
		print cmd
		print "(CVS) CreateRepositoryDir Error (",rc,"): ",err
	return vcs.test(rc)


#
# TestTool:
#	tests if vcs tool is available
#
def TestTool():
	return vcs.is_reachable('cvs')


#
# Constructor:
#
def Constructor():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x @BConstructor@B"
	if not os.path.isdir(vcs.tmpDir):
		vcs.mkdir_p(vcs.tmpDir)
	return TRUE


#
# Destructor:
#
def Destructor():
	if CVSPrefs.TEST: print "(CVS) @r>>>@x Destructor"
	if os.path.isdir(vcs.tmpDir):
		vcs.rm_fr(vcs.tmpDir)
	return TRUE

if __name__ == '__main__':
	dummy=1
	#print cvsTagnameForRevision("D:/home/mober/TestArea/cvs3/complex", "main.C", "1.1.2.2")
